Skip to content

fix: Improved Portainer authentication jwt expiry handling#113

Open
MaxGojko wants to merge 1 commit intodkorecko:mainfrom
MaxGojko:feat-automatic-jwt-expiry-detection
Open

fix: Improved Portainer authentication jwt expiry handling#113
MaxGojko wants to merge 1 commit intodkorecko:mainfrom
MaxGojko:feat-automatic-jwt-expiry-detection

Conversation

@MaxGojko
Copy link
Copy Markdown
Contributor

@MaxGojko MaxGojko commented Jan 21, 2026

Session lifetime is configurable in Portainer under Settings → Authentication → Configuration → Session lifetime. Using a longer session lifetime does not affect the program. However, using a shorter session lifetime of less than 8 hours will result in PatchPanda assuming the jwt is valid and trying to request resources with it. This results in multiple exceptions and Portainer compose stacks not being updated.

This PR improves this behavior by retrieving the expiry from the jwt token itself instead of assuming it to be 8 hours.

Summary by CodeRabbit

  • New Features

    • Implemented JWT token expiry extraction functionality to improve authentication token management and accuracy.
  • Improvements

    • Portainer service authentication now dynamically retrieves and uses the actual JWT token expiry time instead of relying on a hardcoded 8-hour duration, providing more precise token lifecycle management.
    • Enhanced authentication logging with additional diagnostic messages for improved troubleshooting and visibility during service authentication processes.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Jan 21, 2026

📝 Walkthrough

Walkthrough

A new JWT helper class is introduced to extract expiry information from JWT tokens. The Portainer authentication service is updated to use this helper for dynamic token expiry determination instead of a hard-coded 8-hour default, with additional logging added throughout the authentication flow.

Changes

Cohort / File(s) Summary
JWT Helper Utility
PatchPanda.Web/Helpers/JwtHelper.cs
New class with GetJwtExpiry(string jwt) method that decodes JWT payload and extracts Unix timestamp expiry, converting it to UTC DateTime. Includes error handling for invalid JWT structure, missing exp claim, and parsing failures.
Portainer Service Integration
PatchPanda.Web/Services/PortainerService.cs
Updated authentication to use JwtHelper.GetJwtExpiry() for dynamic token expiry extraction with 8-hour fallback. Added log entries before authentication payload and after successful authentication completion.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A helper hops in with JWT delight,
Extracting expiry from tokens so tight,
No more eight-hour guesses we make,
Dynamic timers now—what a breakthrough to celebrate! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: improving Portainer JWT expiry handling by extracting actual expiry from the token instead of assuming a fixed 8-hour lifetime.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@PatchPanda.Web/Helpers/JwtHelper.cs`:
- Around line 7-12: GetJwtExpiry currently accepts null/empty and 2-part strings
which can treat malformed tokens as valid; add an early guard in GetJwtExpiry to
return null if the jwt parameter is null, empty, or whitespace, then require
that jwt.Split('.') yields exactly 3 parts (not >=2) before proceeding; if the
parts count is not 3, return null to avoid parsing invalid tokens and invalid
caching.

In `@PatchPanda.Web/Services/PortainerService.cs`:
- Around line 99-100: Adjust how the JWT expiry is stored to include a small
clock-skew buffer: when setting _jwtExpiry from JwtHelper.GetJwtExpiry(_jwt) (or
the fallback DateTime.UtcNow.AddHours(8)), subtract a short buffer (e.g., 1–2
minutes) so you consider tokens expired slightly before the exact exp claim to
avoid near-expiry/in-flight failures; update the assignment to compute the
expiry value, apply TimeSpan.FromMinutes(1) (or chosen buffer) subtraction, and
store that in _jwtExpiry.

Comment on lines +7 to +12
public static DateTime? GetJwtExpiry(string jwt)
{
try
{
var parts = jwt.Split('.');
if (parts.Length < 2) return null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Guard against null/whitespace and malformed JWTs to avoid invalid caching.

jwt can still be null/empty at runtime, and accepting 2-part tokens can treat malformed JWTs as valid. Add an early guard and require exactly 3 parts.

🛠️ Proposed fix
public static DateTime? GetJwtExpiry(string jwt)
{
+    if (string.IsNullOrWhiteSpace(jwt)) return null;
     try
     {
         var parts = jwt.Split('.');
-        if (parts.Length < 2) return null;
+        if (parts.Length != 3) return null;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public static DateTime? GetJwtExpiry(string jwt)
{
try
{
var parts = jwt.Split('.');
if (parts.Length < 2) return null;
public static DateTime? GetJwtExpiry(string jwt)
{
if (string.IsNullOrWhiteSpace(jwt)) return null;
try
{
var parts = jwt.Split('.');
if (parts.Length != 3) return null;
🤖 Prompt for AI Agents
In `@PatchPanda.Web/Helpers/JwtHelper.cs` around lines 7 - 12, GetJwtExpiry
currently accepts null/empty and 2-part strings which can treat malformed tokens
as valid; add an early guard in GetJwtExpiry to return null if the jwt parameter
is null, empty, or whitespace, then require that jwt.Split('.') yields exactly 3
parts (not >=2) before proceeding; if the parts count is not 3, return null to
avoid parsing invalid tokens and invalid caching.

Comment on lines +99 to +100
_jwtExpiry = JwtHelper.GetJwtExpiry(_jwt) ?? DateTime.UtcNow.AddHours(8);
_logger.LogInformation("Portainer authentication successful.");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider a small clock-skew buffer when storing JWT expiry.

Using the exact exp can cause near-expiry requests to fail if clocks drift or the request is in-flight.

🧩 Suggested tweak
-            _jwtExpiry = JwtHelper.GetJwtExpiry(_jwt) ?? DateTime.UtcNow.AddHours(8);
+            var exp = JwtHelper.GetJwtExpiry(_jwt);
+            _jwtExpiry = exp?.AddSeconds(-60) ?? DateTime.UtcNow.AddHours(8);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
_jwtExpiry = JwtHelper.GetJwtExpiry(_jwt) ?? DateTime.UtcNow.AddHours(8);
_logger.LogInformation("Portainer authentication successful.");
var exp = JwtHelper.GetJwtExpiry(_jwt);
_jwtExpiry = exp?.AddSeconds(-60) ?? DateTime.UtcNow.AddHours(8);
_logger.LogInformation("Portainer authentication successful.");
🤖 Prompt for AI Agents
In `@PatchPanda.Web/Services/PortainerService.cs` around lines 99 - 100, Adjust
how the JWT expiry is stored to include a small clock-skew buffer: when setting
_jwtExpiry from JwtHelper.GetJwtExpiry(_jwt) (or the fallback
DateTime.UtcNow.AddHours(8)), subtract a short buffer (e.g., 1–2 minutes) so you
consider tokens expired slightly before the exact exp claim to avoid
near-expiry/in-flight failures; update the assignment to compute the expiry
value, apply TimeSpan.FromMinutes(1) (or chosen buffer) subtraction, and store
that in _jwtExpiry.

@dkorecko
Copy link
Copy Markdown
Owner

@MaxGojko thanks for this one as well, I'll review once you resolve coderabbit's comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants